package com.yifeng.repo.base.mdm.loader.aop;

import com.gomcarter.frameworks.base.common.AssertUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yifeng.repo.base.mdm.console.constant.MdmTypeEnum;
import com.yifeng.repo.base.mdm.loader.annotation.MdmCache;
import com.yifeng.repo.base.mdm.loader.param.MdmCacheParam;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import javax.annotation.Nonnull;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

/**
 * Created by daibing on 2024/2/25.
 */
@Slf4j
public class MdmCacheAnnotationInterceptor implements MethodInterceptor {
    private final ConcurrentMap<String, MdmTypeEnum> clazz2MdmType = new ConcurrentHashMap<>();
    private final ConcurrentMap<String, Cache<String, Object>> clazz2Cache = new ConcurrentHashMap<>();
    private final List<MdmCacheParam> mdmCacheParams;

    public MdmCacheAnnotationInterceptor(List<MdmCacheParam> mdmCacheParams) {
        this.mdmCacheParams = mdmCacheParams;
    }

    @Override
    public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
        // 1、检查类配置，如果未配置就直接调用服务端接口
        Class<?> clazz = invocation.getMethod().getDeclaringClass();
        MdmTypeEnum mdmType = getMdmTypeByClazz(clazz);
        MdmCacheParam mdmCacheParam = mdmCacheParams.stream()
                .filter(s -> s.getMdmType() == mdmType)
                .findFirst()
                .orElse(null);
        if (mdmCacheParam == null) {
            return invocation.proceed();
        }

        // 2、检查方法配置，如果不启用缓存就直接调用服务端接口
        boolean enableCache = enableCacheByMethod(mdmCacheParam, invocation.getMethod());
        if (!enableCache) {
            return invocation.proceed();
        }

        // 3、匹配本地缓存，如果查询不到数据就直接调用服务端接口
        Cache<String, Object> cache = getCacheByClazz(mdmCacheParam, clazz);
        String key = getCacheKey(invocation.getArguments(), invocation.getMethod());
        Object result = cache.getIfPresent(key);
        if (result != null) {
            return result;
        }

        // 4、直接调用服务端接口
        Object proceed = invocation.proceed();
        if (proceed != null) {
            cache.put(key, proceed);
        }
        return proceed;
    }

    private MdmTypeEnum getMdmTypeByClazz(Class<?> clazz) {
        MdmTypeEnum mdmType = clazz2MdmType.get(clazz.getName());
        if (mdmType != null) {
            return mdmType;
        }
        synchronized (clazz2MdmType) {
            mdmType = clazz2MdmType.get(clazz.getName());
            if (mdmType != null) {
                return mdmType;
            }
            mdmType = findMdmTypeByClazz(clazz);
            AssertUtils.notNull(mdmType, "未识别到主数据类型编码！" + clazz.getName());
            clazz2MdmType.put(clazz.getName(), mdmType);
        }
        return clazz2MdmType.get(clazz.getName());
    }

    private MdmTypeEnum findMdmTypeByClazz(Class<?> clazz) {
        MdmCache declaredAnnotation = clazz.getDeclaredAnnotation(MdmCache.class);
        return MdmTypeEnum.resolve(declaredAnnotation.mdmTypeCode());
    }

    private boolean enableCacheByMethod(MdmCacheParam mdmCacheParam, Method method) {
        // 如果直接指定缓存方法列表，就直接判断是否做缓存
        if (mdmCacheParam.getOnlyMethodNames() != null && !mdmCacheParam.getOnlyMethodNames().isEmpty()) {
            return mdmCacheParam.getOnlyMethodNames().contains(method.getName());
        }
        // 如果排除方法名不是空，并且包含方法名，就不做缓存
        if (mdmCacheParam.getExcludeMethodNames() != null && !mdmCacheParam.getExcludeMethodNames().isEmpty()
                && mdmCacheParam.getExcludeMethodNames().contains(method.getName())) {
            return false;
        }
        // 如果额外方法名不是空，并且包含方法名，就直接做缓存
        if (mdmCacheParam.getExtraMethodNames() != null && !mdmCacheParam.getExtraMethodNames().isEmpty()
                && mdmCacheParam.getExtraMethodNames().contains(method.getName())) {
            return true;
        }
        // 默认的仅对单个入参和单个返回对象的接口做缓存
        return method.getParameterCount() == 1 && !Collection.class.isAssignableFrom(method.getReturnType());
    }

    private Cache<String, Object> getCacheByClazz(MdmCacheParam mdmCacheParam, Class<?> clazz) {
        Cache<String, Object> cache = clazz2Cache.get(clazz.getName());
        if (cache != null) {
            return cache;
        }
        synchronized (clazz2Cache) {
            cache = clazz2Cache.get(clazz.getName());
            if (cache != null) {
                return cache;
            }
            cache = CacheBuilder.newBuilder()
                    .initialCapacity(mdmCacheParam.getInitialCapacity())
                    .maximumSize(mdmCacheParam.getMaximumSize())
                    .concurrencyLevel(16)
                    .expireAfterWrite(mdmCacheParam.getDurationSeconds(), TimeUnit.SECONDS)
                    .build();
            clazz2Cache.put(clazz.getName(), cache);
        }
        return clazz2Cache.get(clazz.getName());
    }

    private String getCacheKey(Object[] arguments, Method method) {
        if (method.getParameterCount() == 1 && arguments[0] instanceof Serializable) {
            return method.getName() + arguments[0];
        }
        StringBuilder sb = new StringBuilder(method.getName());
        for (Object argument : arguments) {
            sb.append(argument);
        }
        return sb.toString();
    }
}
